package com.privasphere.wikidoclet.doclet;

// XMLWriter.java - serialize an XML document.
// Written by David Megginson, david@megginson.com
// and placed by him into the public domain.
// Extensively modified by John Cowan for TagSoup.
// TagSoup is licensed under the Apache License,
// Version 2.0.  You may obtain a copy of this license at
// http://www.apache.org/licenses/LICENSE-2.0 .  You may also have
// additional legal rights not granted by this license.
//
// TagSoup is distributed in the hope that it will be useful, but
// unless required by applicable law or agreed to in writing, TagSoup
// is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS
// OF ANY KIND, either express or implied; not even the implied warranty
// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Properties;

import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
import org.xml.sax.ext.LexicalHandler;
import org.xml.sax.helpers.AttributesImpl;
import org.xml.sax.helpers.NamespaceSupport;
import org.xml.sax.helpers.XMLFilterImpl;

/**
 * Filter to write an XML document from a SAX event stream.
 * 
 * <p>
 * This class can be used by itself or as part of a SAX event stream: it takes
 * as input a series of SAX2 ContentHandler events and uses the information in
 * those events to write an XML document. Since this class is a filter, it can
 * also pass the events on down a filter chain for further processing (you can
 * use the XMLWriter to take a snapshot of the current state at any point in a
 * filter chain), and it can be used directly as a ContentHandler for a SAX2
 * XMLReader.
 * </p>
 * 
 * <p>
 * The client creates a document by invoking the methods for standard SAX2
 * events, always beginning with the {@link #startDocument startDocument} method
 * and ending with the {@link #endDocument endDocument} method. There are
 * convenience methods provided so that clients to not have to create empty
 * attribute lists or provide empty strings as parameters; for example, the
 * method invocation
 * </p>
 * 
 * <pre>
 * w.startElement(&quot;foo&quot;);
 * </pre>
 * 
 * <p>
 * is equivalent to the regular SAX2 ContentHandler method
 * </p>
 * 
 * <pre>
 * w.startElement(&quot;&quot;, &quot;foo&quot;, &quot;&quot;, new AttributesImpl());
 * </pre>
 * 
 * <p>
 * Except that it is more efficient because it does not allocate a new empty
 * attribute list each time. The following code will send a simple XML document
 * to standard output:
 * </p>
 * 
 * <pre>
 * XMLWriter w = new XMLWriter();
 * 
 * w.startDocument();
 * w.startElement(&quot;greeting&quot;);
 * w.characters(&quot;Hello, world!&quot;);
 * w.endElement(&quot;greeting&quot;);
 * w.endDocument();
 * </pre>
 * 
 * <p>
 * The resulting document will look like this:
 * </p>
 * 
 * <pre>
 * &lt;?xml version="1.0" standalone="yes"?>
 * 
 * &lt;greeting>Hello, world!&lt;/greeting>
 * </pre>
 * 
 * <p>
 * In fact, there is an even simpler convenience method, <var>dataElement</var>,
 * designed for writing elements that contain only character data, so the code
 * to generate the document could be shortened to
 * </p>
 * 
 * <pre>
 * XMLWriter w = new XMLWriter();
 * 
 * w.startDocument();
 * w.dataElement(&quot;greeting&quot;, &quot;Hello, world!&quot;);
 * w.endDocument();
 * </pre>
 * 
 * <h2>Whitespace</h2>
 * 
 * <p>
 * According to the XML Recommendation, <em>all</em> whitespace in an XML
 * document is potentially significant to an application, so this class never
 * adds newlines or indentation. If you insert three elements in a row, as in
 * </p>
 * 
 * <pre>
 * w.dataElement(&quot;item&quot;, &quot;1&quot;);
 * w.dataElement(&quot;item&quot;, &quot;2&quot;);
 * w.dataElement(&quot;item&quot;, &quot;3&quot;);
 * </pre>
 * 
 * <p>
 * you will end up with
 * </p>
 * 
 * <pre>
 * &lt;item>1&lt;/item>&lt;item>3&lt;/item>&lt;item>3&lt;/item>
 * </pre>
 * 
 * <p>
 * You need to invoke one of the <var>characters</var> methods explicitly to add
 * newlines or indentation. Alternatively, you can use
 * {@link com.megginson.sax.DataWriter DataWriter}, which is derived from this
 * class -- it is optimized for writing purely data-oriented (or field-oriented)
 * XML, and does automatic linebreaks and indentation (but does not support
 * mixed content properly).
 * </p>
 * 
 * 
 * <h2>Namespace Support</h2>
 * 
 * <p>
 * The writer contains extensive support for XML Namespaces, so that a client
 * application does not have to keep track of prefixes and supply
 * <var>xmlns</var> attributes. By default, the XML writer will generate
 * Namespace declarations in the form _NS1, _NS2, etc., wherever they are
 * needed, as in the following example:
 * </p>
 * 
 * <pre>
 * w.startDocument();
 * w.emptyElement(&quot;http://www.foo.com/ns/&quot;, &quot;foo&quot;);
 * w.endDocument();
 * </pre>
 * 
 * <p>
 * The resulting document will look like this:
 * </p>
 * 
 * <pre>
 * &lt;?xml version="1.0" standalone="yes"?>
 * 
 * &lt;_NS1:foo xmlns:_NS1="http://www.foo.com/ns/"/>
 * </pre>
 * 
 * <p>
 * In many cases, document authors will prefer to choose their own prefixes
 * rather than using the (ugly) default names. The XML writer allows two methods
 * for selecting prefixes:
 * </p>
 * 
 * <ol>
 * <li>the qualified name</li>
 * <li>the {@link #setPrefix setPrefix} method.</li>
 * </ol>
 * 
 * <p>
 * Whenever the XML writer finds a new Namespace URI, it checks to see if a
 * qualified (prefixed) name is also available; if so it attempts to use the
 * name's prefix (as long as the prefix is not already in use for another
 * Namespace URI).
 * </p>
 * 
 * <p>
 * Before writing a document, the client can also pre-map a prefix to a
 * Namespace URI with the setPrefix method:
 * </p>
 * 
 * <pre>
 * w.setPrefix(&quot;http://www.foo.com/ns/&quot;, &quot;foo&quot;);
 * w.startDocument();
 * w.emptyElement(&quot;http://www.foo.com/ns/&quot;, &quot;foo&quot;);
 * w.endDocument();
 * </pre>
 * 
 * <p>
 * The resulting document will look like this:
 * </p>
 * 
 * <pre>
 * &lt;?xml version="1.0" standalone="yes"?>
 * 
 * &lt;foo:foo xmlns:foo="http://www.foo.com/ns/"/>
 * </pre>
 * 
 * <p>
 * The default Namespace simply uses an empty string as the prefix:
 * </p>
 * 
 * <pre>
 * w.setPrefix(&quot;http://www.foo.com/ns/&quot;, &quot;&quot;);
 * w.startDocument();
 * w.emptyElement(&quot;http://www.foo.com/ns/&quot;, &quot;foo&quot;);
 * w.endDocument();
 * </pre>
 * 
 * <p>
 * The resulting document will look like this:
 * </p>
 * 
 * <pre>
 * &lt;?xml version="1.0" standalone="yes"?>
 * 
 * &lt;foo xmlns="http://www.foo.com/ns/"/>
 * </pre>
 * 
 * <p>
 * By default, the XML writer will not declare a Namespace until it is actually
 * used. Sometimes, this approach will create a large number of Namespace
 * declarations, as in the following example:
 * </p>
 * 
 * <pre>
 * &lt;xml version="1.0" standalone="yes"?>
 * 
 * &lt;rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
 *  &lt;rdf:Description about="http://www.foo.com/ids/books/12345">
 *   &lt;dc:title xmlns:dc="http://www.purl.org/dc/">A Dark Night&lt;/dc:title>
 *   &lt;dc:creator xmlns:dc="http://www.purl.org/dc/">Jane Smith&lt;/dc:title>
 *   &lt;dc:date xmlns:dc="http://www.purl.org/dc/">2000-09-09&lt;/dc:title>
 *  &lt;/rdf:Description>
 * &lt;/rdf:RDF>
 * </pre>
 * 
 * <p>
 * The "rdf" prefix is declared only once, because the RDF Namespace is used by
 * the root element and can be inherited by all of its descendants; the "dc"
 * prefix, on the other hand, is declared three times, because no higher element
 * uses the Namespace. To solve this problem, you can instruct the XML writer to
 * predeclare Namespaces on the root element even if they are not used there:
 * </p>
 * 
 * <pre>
 * w.forceNSDecl(&quot;http://www.purl.org/dc/&quot;);
 * </pre>
 * 
 * <p>
 * Now, the "dc" prefix will be declared on the root element even though it's
 * not needed there, and can be inherited by its descendants:
 * </p>
 * 
 * <pre>
 * &lt;xml version="1.0" standalone="yes"?>
 * 
 * &lt;rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
 *             xmlns:dc="http://www.purl.org/dc/">
 *  &lt;rdf:Description about="http://www.foo.com/ids/books/12345">
 *   &lt;dc:title>A Dark Night&lt;/dc:title>
 *   &lt;dc:creator>Jane Smith&lt;/dc:title>
 *   &lt;dc:date>2000-09-09&lt;/dc:title>
 *  &lt;/rdf:Description>
 * &lt;/rdf:RDF>
 * </pre>
 * 
 * <p>
 * This approach is also useful for declaring Namespace prefixes that be used by
 * qualified names appearing in attribute values or character data.
 * </p>
 * 
 * @author David Megginson, david@megginson.com
 * @version 0.2
 * @see org.xml.sax.XMLFilter
 * @see org.xml.sax.ContentHandler
 */
public class XMLWriterHandler extends XMLFilterImpl implements LexicalHandler {

	// //////////////////////////////////////////////////////////////////
	// Constructors.
	// //////////////////////////////////////////////////////////////////

	/**
	 * Create a new XML writer.
	 * 
	 * <p>
	 * Write to standard output.
	 * </p>
	 */
	public XMLWriterHandler() {
		init(null);
	}

	/**
	 * Create a new XML writer.
	 * 
	 * <p>
	 * Write to the writer provided.
	 * </p>
	 * 
	 * @param writer
	 *            The output destination, or null to use standard output.
	 */
	public XMLWriterHandler(Writer writer) {
		init(writer);
	}

	/**
	 * Create a new XML writer.
	 * 
	 * <p>
	 * Use the specified XML reader as the parent.
	 * </p>
	 * 
	 * @param xmlreader
	 *            The parent in the filter chain, or null for no parent.
	 */
	public XMLWriterHandler(XMLReader xmlreader) {
		super(xmlreader);
		init(null);
	}

	/**
	 * Create a new XML writer.
	 * 
	 * <p>
	 * Use the specified XML reader as the parent, and write to the specified
	 * writer.
	 * </p>
	 * 
	 * @param xmlreader
	 *            The parent in the filter chain, or null for no parent.
	 * @param writer
	 *            The output destination, or null to use standard output.
	 */
	public XMLWriterHandler(XMLReader xmlreader, Writer writer) {
		super(xmlreader);
		init(writer);
	}

	/**
	 * Internal initialization method.
	 * 
	 * <p>
	 * All of the public constructors invoke this method.
	 * 
	 * @param writer
	 *            The output destination, or null to use standard output.
	 */
	@SuppressWarnings("rawtypes")
	private void init(Writer writer) {
		setOutput(writer);
		nsSupport = new NamespaceSupport();
		prefixTable = new Hashtable();
		forcedDeclTable = new Hashtable();
		doneDeclTable = new Hashtable();
		outputProperties = new Properties();
	}

	// //////////////////////////////////////////////////////////////////
	// Public methods.
	// //////////////////////////////////////////////////////////////////

	/**
	 * Reset the writer.
	 * 
	 * <p>
	 * This method is especially useful if the writer throws an exception before
	 * it is finished, and you want to reuse the writer for a new document. It
	 * is usually a good idea to invoke {@link #flush flush} before resetting
	 * the writer, to make sure that no output is lost.
	 * </p>
	 * 
	 * <p>
	 * This method is invoked automatically by the {@link #startDocument
	 * startDocument} method before writing a new document.
	 * </p>
	 * 
	 * <p>
	 * <strong>Note:</strong> this method will <em>not</em> clear the prefix or
	 * URI information in the writer or the selected output writer.
	 * </p>
	 * 
	 * @see #flush
	 */
	public void reset() {
		elementLevel = 0;
		prefixCounter = 0;
		nsSupport.reset();
	}

	/**
	 * Flush the output.
	 * 
	 * <p>
	 * This method flushes the output stream. It is especially useful when you
	 * need to make certain that the entire document has been written to output
	 * but do not want to close the output stream.
	 * </p>
	 * 
	 * <p>
	 * This method is invoked automatically by the {@link #endDocument
	 * endDocument} method after writing a document.
	 * </p>
	 * 
	 * @see #reset
	 */
	public void flush() throws IOException {
		output.flush();
	}

	/**
	 * Set a new output destination for the document.
	 * 
	 * @param writer
	 *            The output destination, or null to use standard output.
	 * @return The current output writer.
	 * @see #flush
	 */
	public void setOutput(Writer writer) {
		if (writer == null) {
			output = new OutputStreamWriter(System.out);
		} else {
			output = writer;
		}
	}

	/**
	 * Specify a preferred prefix for a Namespace URI.
	 * 
	 * <p>
	 * Note that this method does not actually force the Namespace to be
	 * declared; to do that, use the {@link #forceNSDecl(java.lang.String)
	 * forceNSDecl} method as well.
	 * </p>
	 * 
	 * @param uri
	 *            The Namespace URI.
	 * @param prefix
	 *            The preferred prefix, or "" to select the default Namespace.
	 * @see #getPrefix
	 * @see #forceNSDecl(java.lang.String)
	 * @see #forceNSDecl(java.lang.String,java.lang.String)
	 */
	@SuppressWarnings("unchecked")
	public void setPrefix(String uri, String prefix) {
		prefixTable.put(uri, prefix);
	}

	/**
	 * Get the current or preferred prefix for a Namespace URI.
	 * 
	 * @param uri
	 *            The Namespace URI.
	 * @return The preferred prefix, or "" for the default Namespace.
	 * @see #setPrefix
	 */
	public String getPrefix(String uri) {
		return (String) prefixTable.get(uri);
	}

	/**
	 * Force a Namespace to be declared on the root element.
	 * 
	 * <p>
	 * By default, the XMLWriter will declare only the Namespaces needed for an
	 * element; as a result, a Namespace may be declared many places in a
	 * document if it is not used on the root element.
	 * </p>
	 * 
	 * <p>
	 * This method forces a Namespace to be declared on the root element even if
	 * it is not used there, and reduces the number of xmlns attributes in the
	 * document.
	 * </p>
	 * 
	 * @param uri
	 *            The Namespace URI to declare.
	 * @see #forceNSDecl(java.lang.String,java.lang.String)
	 * @see #setPrefix
	 */
	@SuppressWarnings("unchecked")
	public void forceNSDecl(String uri) {
		forcedDeclTable.put(uri, Boolean.TRUE);
	}

	/**
	 * Force a Namespace declaration with a preferred prefix.
	 * 
	 * <p>
	 * This is a convenience method that invokes {@link #setPrefix setPrefix}
	 * then {@link #forceNSDecl(java.lang.String) forceNSDecl}.
	 * </p>
	 * 
	 * @param uri
	 *            The Namespace URI to declare on the root element.
	 * @param prefix
	 *            The preferred prefix for the Namespace, or "" for the default
	 *            Namespace.
	 * @see #setPrefix
	 * @see #forceNSDecl(java.lang.String)
	 */
	public void forceNSDecl(String uri, String prefix) {
		setPrefix(uri, prefix);
		forceNSDecl(uri);
	}

	// //////////////////////////////////////////////////////////////////
	// Methods from org.xml.sax.ContentHandler.
	// //////////////////////////////////////////////////////////////////

	/**
	 * Write the XML declaration at the beginning of the document.
	 * 
	 * Pass the event on down the filter chain for further processing.
	 * 
	 * @exception org.xml.sax.SAXException
	 *                If there is an error writing the XML declaration, or if a
	 *                handler further down the filter chain raises an exception.
	 * @see org.xml.sax.ContentHandler#startDocument
	 */
	public void startDocument() throws SAXException {
		reset();
		if (!("yes".equals(outputProperties.getProperty(OMIT_XML_DECLARATION, "no")))) {
			write("<?xml");
			if (version == null) {
				write(" version=\"1.0\"");
			} else {
				write(" version=\"");
				write(version);
				write("\"");
			}
			if (outputEncoding != null && outputEncoding != "") {
				write(" encoding=\"");
				write(outputEncoding);
				write("\"");
			}
			if (standalone == null) {
				write(" standalone=\"yes\"?>\n");
			} else {
				write(" standalone=\"");
				write(standalone);
				write("\"");
			}
		}
		super.startDocument();
	}

	/**
	 * Write a newline at the end of the document.
	 * 
	 * Pass the event on down the filter chain for further processing.
	 * 
	 * @exception org.xml.sax.SAXException
	 *                If there is an error writing the newline, or if a handler
	 *                further down the filter chain raises an exception.
	 * @see org.xml.sax.ContentHandler#endDocument
	 */
	public void endDocument() throws SAXException {
		write('\n');
		super.endDocument();
		try {
			flush();
		} catch (IOException e) {
			throw new SAXException(e);
		}
	}

	/**
	 * Write a start tag.
	 * 
	 * Pass the event on down the filter chain for further processing.
	 * 
	 * @param uri
	 *            The Namespace URI, or the empty string if none is available.
	 * @param localName
	 *            The element's local (unprefixed) name (required).
	 * @param qName
	 *            The element's qualified (prefixed) name, or the empty string
	 *            is none is available. This method will use the qName as a
	 *            template for generating a prefix if necessary, but it is not
	 *            guaranteed to use the same qName.
	 * @param atts
	 *            The element's attribute list (must not be null).
	 * @exception org.xml.sax.SAXException
	 *                If there is an error writing the start tag, or if a
	 *                handler further down the filter chain raises an exception.
	 * @see org.xml.sax.ContentHandler#startElement
	 */
	public void startElement(String uri, String localName, String qName, Attributes atts) throws SAXException {
		elementLevel++;
		nsSupport.pushContext();
		if (forceDTD && !hasOutputDTD)
			startDTD(localName == null ? qName : localName, "", "");
		write('<');
		writeName(uri, localName, qName, true);
		writeAttributes(atts);
		if (elementLevel == 1) {
			forceNSDecls();
		}
		writeNSDecls();
		write('>');
		// System.out.println("%%%% startElement [" + qName + "] htmlMode = " +
		// htmlMode);
		if (htmlMode && (qName.equals("script") || qName.equals("style"))) {
			cdataElement = true;
			// System.out.println("%%%% CDATA element");
		}
		super.startElement(uri, localName, qName, atts);
	}

	/**
	 * Write an end tag.
	 * 
	 * Pass the event on down the filter chain for further processing.
	 * 
	 * @param uri
	 *            The Namespace URI, or the empty string if none is available.
	 * @param localName
	 *            The element's local (unprefixed) name (required).
	 * @param qName
	 *            The element's qualified (prefixed) name, or the empty string
	 *            is none is available. This method will use the qName as a
	 *            template for generating a prefix if necessary, but it is not
	 *            guaranteed to use the same qName.
	 * @exception org.xml.sax.SAXException
	 *                If there is an error writing the end tag, or if a handler
	 *                further down the filter chain raises an exception.
	 * @see org.xml.sax.ContentHandler#endElement
	 */
	public void endElement(String uri, String localName, String qName) throws SAXException {
		if (!(htmlMode && (uri.equals("http://www.w3.org/1999/xhtml") || uri.equals("")) && (qName.equals("area") || qName.equals("base")
				|| qName.equals("basefont") || qName.equals("br") || qName.equals("col") || qName.equals("frame") || qName.equals("hr") || qName.equals("img")
				|| qName.equals("input") || qName.equals("isindex") || qName.equals("link") || qName.equals("meta") || qName.equals("param")))) {
			write("</");
			writeName(uri, localName, qName, true);
			write('>');
		}
		if (elementLevel == 1) {
			write('\n');
		}
		cdataElement = false;
		super.endElement(uri, localName, qName);
		nsSupport.popContext();
		elementLevel--;
	}

	/**
	 * Write character data.
	 * 
	 * Pass the event on down the filter chain for further processing.
	 * 
	 * @param ch
	 *            The array of characters to write.
	 * @param start
	 *            The starting position in the array.
	 * @param length
	 *            The number of characters to write.
	 * @exception org.xml.sax.SAXException
	 *                If there is an error writing the characters, or if a
	 *                handler further down the filter chain raises an exception.
	 * @see org.xml.sax.ContentHandler#characters
	 */
	public void characters(char ch[], int start, int len) throws SAXException {
		if (!cdataElement) {
			writeEsc(ch, start, len, false);
		} else {
			for (int i = start; i < start + len; i++) {
				write(ch[i]);
			}
		}
		super.characters(ch, start, len);
	}

	/**
	 * Write ignorable whitespace.
	 * 
	 * Pass the event on down the filter chain for further processing.
	 * 
	 * @param ch
	 *            The array of characters to write.
	 * @param start
	 *            The starting position in the array.
	 * @param length
	 *            The number of characters to write.
	 * @exception org.xml.sax.SAXException
	 *                If there is an error writing the whitespace, or if a
	 *                handler further down the filter chain raises an exception.
	 * @see org.xml.sax.ContentHandler#ignorableWhitespace
	 */
	public void ignorableWhitespace(char ch[], int start, int length) throws SAXException {
		writeEsc(ch, start, length, false);
		super.ignorableWhitespace(ch, start, length);
	}

	/**
	 * Write a processing instruction.
	 * 
	 * Pass the event on down the filter chain for further processing.
	 * 
	 * @param target
	 *            The PI target.
	 * @param data
	 *            The PI data.
	 * @exception org.xml.sax.SAXException
	 *                If there is an error writing the PI, or if a handler
	 *                further down the filter chain raises an exception.
	 * @see org.xml.sax.ContentHandler#processingInstruction
	 */
	public void processingInstruction(String target, String data) throws SAXException {
		write("<?");
		write(target);
		write(' ');
		write(data);
		write("?>");
		if (elementLevel < 1) {
			write('\n');
		}
		super.processingInstruction(target, data);
	}

	// //////////////////////////////////////////////////////////////////
	// Additional markup.
	// //////////////////////////////////////////////////////////////////

	/**
	 * Write an empty element.
	 * 
	 * This method writes an empty element tag rather than a start tag followed
	 * by an end tag. Both a {@link #startElement startElement} and an
	 * {@link #endElement endElement} event will be passed on down the filter
	 * chain.
	 * 
	 * @param uri
	 *            The element's Namespace URI, or the empty string if the
	 *            element has no Namespace or if Namespace processing is not
	 *            being performed.
	 * @param localName
	 *            The element's local name (without prefix). This parameter must
	 *            be provided.
	 * @param qName
	 *            The element's qualified name (with prefix), or the empty
	 *            string if none is available. This parameter is strictly
	 *            advisory: the writer may or may not use the prefix attached.
	 * @param atts
	 *            The element's attribute list.
	 * @exception org.xml.sax.SAXException
	 *                If there is an error writing the empty tag, or if a
	 *                handler further down the filter chain raises an exception.
	 * @see #startElement
	 * @see #endElement
	 */
	public void emptyElement(String uri, String localName, String qName, Attributes atts) throws SAXException {
		nsSupport.pushContext();
		write('<');
		writeName(uri, localName, qName, true);
		writeAttributes(atts);
		if (elementLevel == 1) {
			forceNSDecls();
		}
		writeNSDecls();
		write("/>");
		super.startElement(uri, localName, qName, atts);
		super.endElement(uri, localName, qName);
	}

	// //////////////////////////////////////////////////////////////////
	// Convenience methods.
	// //////////////////////////////////////////////////////////////////

	/**
	 * Start a new element without a qname or attributes.
	 * 
	 * <p>
	 * This method will provide a default empty attribute list and an empty
	 * string for the qualified name. It invokes
	 * {@link #startElement(String, String, String, Attributes)} directly.
	 * </p>
	 * 
	 * @param uri
	 *            The element's Namespace URI.
	 * @param localName
	 *            The element's local name.
	 * @exception org.xml.sax.SAXException
	 *                If there is an error writing the start tag, or if a
	 *                handler further down the filter chain raises an exception.
	 * @see #startElement(String, String, String, Attributes)
	 */
	public void startElement(String uri, String localName) throws SAXException {
		startElement(uri, localName, "", EMPTY_ATTS);
	}

	/**
	 * Start a new element without a qname, attributes or a Namespace URI.
	 * 
	 * <p>
	 * This method will provide an empty string for the Namespace URI, and empty
	 * string for the qualified name, and a default empty attribute list. It
	 * invokes #startElement(String, String, String, Attributes)} directly.
	 * </p>
	 * 
	 * @param localName
	 *            The element's local name.
	 * @exception org.xml.sax.SAXException
	 *                If there is an error writing the start tag, or if a
	 *                handler further down the filter chain raises an exception.
	 * @see #startElement(String, String, String, Attributes)
	 */
	public void startElement(String localName) throws SAXException {
		startElement("", localName, "", EMPTY_ATTS);
	}

	/**
	 * End an element without a qname.
	 * 
	 * <p>
	 * This method will supply an empty string for the qName. It invokes
	 * {@link #endElement(String, String, String)} directly.
	 * </p>
	 * 
	 * @param uri
	 *            The element's Namespace URI.
	 * @param localName
	 *            The element's local name.
	 * @exception org.xml.sax.SAXException
	 *                If there is an error writing the end tag, or if a handler
	 *                further down the filter chain raises an exception.
	 * @see #endElement(String, String, String)
	 */
	public void endElement(String uri, String localName) throws SAXException {
		endElement(uri, localName, "");
	}

	/**
	 * End an element without a Namespace URI or qname.
	 * 
	 * <p>
	 * This method will supply an empty string for the qName and an empty string
	 * for the Namespace URI. It invokes
	 * {@link #endElement(String, String, String)} directly.
	 * </p>
	 * 
	 * @param localName
	 *            The element's local name.
	 * @exception org.xml.sax.SAXException
	 *                If there is an error writing the end tag, or if a handler
	 *                further down the filter chain raises an exception.
	 * @see #endElement(String, String, String)
	 */
	public void endElement(String localName) throws SAXException {
		endElement("", localName, "");
	}

	/**
	 * Add an empty element without a qname or attributes.
	 * 
	 * <p>
	 * This method will supply an empty string for the qname and an empty
	 * attribute list. It invokes
	 * {@link #emptyElement(String, String, String, Attributes)} directly.
	 * </p>
	 * 
	 * @param uri
	 *            The element's Namespace URI.
	 * @param localName
	 *            The element's local name.
	 * @exception org.xml.sax.SAXException
	 *                If there is an error writing the empty tag, or if a
	 *                handler further down the filter chain raises an exception.
	 * @see #emptyElement(String, String, String, Attributes)
	 */
	public void emptyElement(String uri, String localName) throws SAXException {
		emptyElement(uri, localName, "", EMPTY_ATTS);
	}

	/**
	 * Add an empty element without a Namespace URI, qname or attributes.
	 * 
	 * <p>
	 * This method will supply an empty string for the qname, and empty string
	 * for the Namespace URI, and an empty attribute list. It invokes
	 * {@link #emptyElement(String, String, String, Attributes)} directly.
	 * </p>
	 * 
	 * @param localName
	 *            The element's local name.
	 * @exception org.xml.sax.SAXException
	 *                If there is an error writing the empty tag, or if a
	 *                handler further down the filter chain raises an exception.
	 * @see #emptyElement(String, String, String, Attributes)
	 */
	public void emptyElement(String localName) throws SAXException {
		emptyElement("", localName, "", EMPTY_ATTS);
	}

	/**
	 * Write an element with character data content.
	 * 
	 * <p>
	 * This is a convenience method to write a complete element with character
	 * data content, including the start tag and end tag.
	 * </p>
	 * 
	 * <p>
	 * This method invokes
	 * {@link #startElement(String, String, String, Attributes)}, followed by
	 * {@link #characters(String)}, followed by
	 * {@link #endElement(String, String, String)}.
	 * </p>
	 * 
	 * @param uri
	 *            The element's Namespace URI.
	 * @param localName
	 *            The element's local name.
	 * @param qName
	 *            The element's default qualified name.
	 * @param atts
	 *            The element's attributes.
	 * @param content
	 *            The character data content.
	 * @exception org.xml.sax.SAXException
	 *                If there is an error writing the empty tag, or if a
	 *                handler further down the filter chain raises an exception.
	 * @see #startElement(String, String, String, Attributes)
	 * @see #characters(String)
	 * @see #endElement(String, String, String)
	 */
	public void dataElement(String uri, String localName, String qName, Attributes atts, String content) throws SAXException {
		startElement(uri, localName, qName, atts);
		characters(content);
		endElement(uri, localName, qName);
	}

	/**
	 * Write an element with character data content but no attributes.
	 * 
	 * <p>
	 * This is a convenience method to write a complete element with character
	 * data content, including the start tag and end tag. This method provides
	 * an empty string for the qname and an empty attribute list.
	 * </p>
	 * 
	 * <p>
	 * This method invokes
	 * {@link #startElement(String, String, String, Attributes)}, followed by
	 * {@link #characters(String)}, followed by
	 * {@link #endElement(String, String, String)}.
	 * </p>
	 * 
	 * @param uri
	 *            The element's Namespace URI.
	 * @param localName
	 *            The element's local name.
	 * @param content
	 *            The character data content.
	 * @exception org.xml.sax.SAXException
	 *                If there is an error writing the empty tag, or if a
	 *                handler further down the filter chain raises an exception.
	 * @see #startElement(String, String, String, Attributes)
	 * @see #characters(String)
	 * @see #endElement(String, String, String)
	 */
	public void dataElement(String uri, String localName, String content) throws SAXException {
		dataElement(uri, localName, "", EMPTY_ATTS, content);
	}

	/**
	 * Write an element with character data content but no attributes or
	 * Namespace URI.
	 * 
	 * <p>
	 * This is a convenience method to write a complete element with character
	 * data content, including the start tag and end tag. The method provides an
	 * empty string for the Namespace URI, and empty string for the qualified
	 * name, and an empty attribute list.
	 * </p>
	 * 
	 * <p>
	 * This method invokes
	 * {@link #startElement(String, String, String, Attributes)}, followed by
	 * {@link #characters(String)}, followed by
	 * {@link #endElement(String, String, String)}.
	 * </p>
	 * 
	 * @param localName
	 *            The element's local name.
	 * @param content
	 *            The character data content.
	 * @exception org.xml.sax.SAXException
	 *                If there is an error writing the empty tag, or if a
	 *                handler further down the filter chain raises an exception.
	 * @see #startElement(String, String, String, Attributes)
	 * @see #characters(String)
	 * @see #endElement(String, String, String)
	 */
	public void dataElement(String localName, String content) throws SAXException {
		dataElement("", localName, "", EMPTY_ATTS, content);
	}

	/**
	 * Write a string of character data, with XML escaping.
	 * 
	 * <p>
	 * This is a convenience method that takes an XML String, converts it to a
	 * character array, then invokes {@link #characters(char[], int, int)}.
	 * </p>
	 * 
	 * @param data
	 *            The character data.
	 * @exception org.xml.sax.SAXException
	 *                If there is an error writing the string, or if a handler
	 *                further down the filter chain raises an exception.
	 * @see #characters(char[], int, int)
	 */
	public void characters(String data) throws SAXException {
		char ch[] = data.toCharArray();
		characters(ch, 0, ch.length);
	}

	// //////////////////////////////////////////////////////////////////
	// Internal methods.
	// //////////////////////////////////////////////////////////////////

	/**
	 * Force all Namespaces to be declared.
	 * 
	 * This method is used on the root element to ensure that the predeclared
	 * Namespaces all appear.
	 */
	@SuppressWarnings("rawtypes")
	private void forceNSDecls() {
		Enumeration prefixes = forcedDeclTable.keys();
		while (prefixes.hasMoreElements()) {
			String prefix = (String) prefixes.nextElement();
			doPrefix(prefix, null, true);
		}
	}

	/**
	 * Determine the prefix for an element or attribute name.
	 * 
	 * TODO: this method probably needs some cleanup.
	 * 
	 * @param uri
	 *            The Namespace URI.
	 * @param qName
	 *            The qualified name (optional); this will be used to indicate
	 *            the preferred prefix if none is currently bound.
	 * @param isElement
	 *            true if this is an element name, false if it is an attribute
	 *            name (which cannot use the default Namespace).
	 */
	@SuppressWarnings("unchecked")
	private String doPrefix(String uri, String qName, boolean isElement) {
		String defaultNS = nsSupport.getURI("");
		if ("".equals(uri)) {
			if (isElement && defaultNS != null)
				nsSupport.declarePrefix("", "");
			return null;
		}
		String prefix;
		if (isElement && defaultNS != null && uri.equals(defaultNS)) {
			prefix = "";
		} else {
			prefix = nsSupport.getPrefix(uri);
		}
		if (prefix != null) {
			return prefix;
		}
		prefix = (String) doneDeclTable.get(uri);
		if (prefix != null && ((!isElement || defaultNS != null) && "".equals(prefix) || nsSupport.getURI(prefix) != null)) {
			prefix = null;
		}
		if (prefix == null) {
			prefix = (String) prefixTable.get(uri);
			if (prefix != null && ((!isElement || defaultNS != null) && "".equals(prefix) || nsSupport.getURI(prefix) != null)) {
				prefix = null;
			}
		}
		if (prefix == null && qName != null && !"".equals(qName)) {
			int i = qName.indexOf(':');
			if (i == -1) {
				if (isElement && defaultNS == null) {
					prefix = "";
				}
			} else {
				prefix = qName.substring(0, i);
			}
		}
		for (; prefix == null || nsSupport.getURI(prefix) != null; prefix = "__NS" + ++prefixCounter)
			;
		nsSupport.declarePrefix(prefix, uri);
		doneDeclTable.put(uri, prefix);
		return prefix;
	}

	/**
	 * Write a raw character.
	 * 
	 * @param c
	 *            The character to write.
	 * @exception org.xml.sax.SAXException
	 *                If there is an error writing the character, this method
	 *                will throw an IOException wrapped in a SAXException.
	 */
	private void write(char c) throws SAXException {
		try {
			output.write(c);
		} catch (IOException e) {
			throw new SAXException(e);
		}
	}

	/**
	 * Write a raw string.
	 * 
	 * @param s
	 * @exception org.xml.sax.SAXException
	 *                If there is an error writing the string, this method will
	 *                throw an IOException wrapped in a SAXException
	 */
	private void write(String s) throws SAXException {
		try {
			output.write(s);
		} catch (IOException e) {
			throw new SAXException(e);
		}
	}

	/**
	 * Write out an attribute list, escaping values.
	 * 
	 * The names will have prefixes added to them.
	 * 
	 * @param atts
	 *            The attribute list to write.
	 * @exception org.xml.SAXException
	 *                If there is an error writing the attribute list, this
	 *                method will throw an IOException wrapped in a
	 *                SAXException.
	 */
	private void writeAttributes(Attributes atts) throws SAXException {
		int len = atts.getLength();
		for (int i = 0; i < len; i++) {
			char ch[] = atts.getValue(i).toCharArray();
			write(' ');
			writeName(atts.getURI(i), atts.getLocalName(i), atts.getQName(i), false);
			if (htmlMode && booleanAttribute(atts.getLocalName(i), atts.getQName(i), atts.getValue(i)))
				break;
			write("=\"");
			writeEsc(ch, 0, ch.length, true);
			write('"');
		}
	}

	private String[] booleans = { "checked", "compact", "declare", "defer", "disabled", "ismap", "multiple", "nohref", "noresize", "noshade", "nowrap",
			"readonly", "selected" };

	// Return true if the attribute is an HTML boolean from the above list.
	private boolean booleanAttribute(String localName, String qName, String value) {
		String name = localName;
		if (name == null) {
			int i = qName.indexOf(':');
			if (i != -1)
				name = qName.substring(i + 1, qName.length());
		}
		if (!name.equals(value))
			return false;
		for (int j = 0; j < booleans.length; j++) {
			if (name.equals(booleans[j]))
				return true;
		}
		return false;
	}

	/**
	 * Write an array of data characters with escaping.
	 * 
	 * @param ch
	 *            The array of characters.
	 * @param start
	 *            The starting position.
	 * @param length
	 *            The number of characters to use.
	 * @param isAttVal
	 *            true if this is an attribute value literal.
	 * @exception org.xml.SAXException
	 *                If there is an error writing the characters, this method
	 *                will throw an IOException wrapped in a SAXException.
	 */
	private void writeEsc(char ch[], int start, int length, boolean isAttVal) throws SAXException {
		for (int i = start; i < start + length; i++) {
			switch (ch[i]) {
			case '&':
				write("&amp;");
				break;
			case '<':
				write("&lt;");
				break;
			case '>':
				write("&gt;");
				break;
			case '\"':
				if (isAttVal) {
					write("&quot;");
				} else {
					write('\"');
				}
				break;
			default:
				if (!unicodeMode && ch[i] > '\u007f') {
					write("&#");
					write(Integer.toString(ch[i]));
					write(';');
				} else {
					write(ch[i]);
				}
			}
		}
	}

	/**
	 * Write out the list of Namespace declarations.
	 * 
	 * @exception org.xml.sax.SAXException
	 *                This method will throw an IOException wrapped in a
	 *                SAXException if there is an error writing the Namespace
	 *                declarations.
	 */
	@SuppressWarnings("rawtypes")
	private void writeNSDecls() throws SAXException {
		Enumeration prefixes = nsSupport.getDeclaredPrefixes();
		while (prefixes.hasMoreElements()) {
			String prefix = (String) prefixes.nextElement();
			String uri = nsSupport.getURI(prefix);
			if (uri == null) {
				uri = "";
			}
			char ch[] = uri.toCharArray();
			write(' ');
			if ("".equals(prefix)) {
				write("xmlns=\"");
			} else {
				write("xmlns:");
				write(prefix);
				write("=\"");
			}
			writeEsc(ch, 0, ch.length, true);
			write('\"');
		}
	}

	/**
	 * Write an element or attribute name.
	 * 
	 * @param uri
	 *            The Namespace URI.
	 * @param localName
	 *            The local name.
	 * @param qName
	 *            The prefixed name, if available, or the empty string.
	 * @param isElement
	 *            true if this is an element name, false if it is an attribute
	 *            name.
	 * @exception org.xml.sax.SAXException
	 *                This method will throw an IOException wrapped in a
	 *                SAXException if there is an error writing the name.
	 */
	private void writeName(String uri, String localName, String qName, boolean isElement) throws SAXException {
		String prefix = doPrefix(uri, qName, isElement);
		if (prefix != null && !"".equals(prefix)) {
			write(prefix);
			write(':');
		}
		if (localName != null && !"".equals(localName)) {
			write(localName);
		} else {
			int i = qName.indexOf(':');
			write(qName.substring(i + 1, qName.length()));
		}
	}

	// //////////////////////////////////////////////////////////////////
	// Default LexicalHandler implementation
	// //////////////////////////////////////////////////////////////////

	public void comment(char[] ch, int start, int length) throws SAXException {
		write("<!--");
		for (int i = start; i < start + length; i++) {
			write(ch[i]);
			if (ch[i] == '-' && i + 1 <= start + length && ch[i + 1] == '-')
				write(' ');
		}
		write("-->");
	}

	public void endCDATA() throws SAXException {
	}

	public void endDTD() throws SAXException {
	}

	public void endEntity(String name) throws SAXException {
	}

	public void startCDATA() throws SAXException {
	}

	public void startDTD(String name, String publicid, String systemid) throws SAXException {
		if (name == null)
			return; // can't cope
		if (hasOutputDTD)
			return; // only one DTD
		hasOutputDTD = true;
		write("<!DOCTYPE ");
		write(name);
		if (systemid == null)
			systemid = "";
		if (overrideSystem != null)
			systemid = overrideSystem;
		char sysquote = (systemid.indexOf('"') != -1) ? '\'' : '"';
		if (overridePublic != null)
			publicid = overridePublic;
		if (!(publicid == null || "".equals(publicid))) {
			char pubquote = (publicid.indexOf('"') != -1) ? '\'' : '"';
			write(" PUBLIC ");
			write(pubquote);
			write(publicid);
			write(pubquote);
			write(' ');
		} else {
			write(" SYSTEM ");
		}
		write(sysquote);
		write(systemid);
		write(sysquote);
		write(">\n");
	}

	public void startEntity(String name) throws SAXException {
	}

	// //////////////////////////////////////////////////////////////////
	// Output properties
	// //////////////////////////////////////////////////////////////////

	public String getOutputProperty(String key) {
		return outputProperties.getProperty(key);
	}

	public void setOutputProperty(String key, String value) {
		outputProperties.setProperty(key, value);
		// System.out.println("%%%% key = [" + key + "] value = [" + value
		// +"]");
		if (key.equals(ENCODING)) {
			outputEncoding = value;
			unicodeMode = value.substring(0, 3).equalsIgnoreCase("utf");
			// System.out.println("%%%% unicodeMode = " + unicodeMode);
		} else if (key.equals(METHOD)) {
			htmlMode = value.equals("html");
		} else if (key.equals(DOCTYPE_PUBLIC)) {
			overridePublic = value;
			forceDTD = true;
		} else if (key.equals(DOCTYPE_SYSTEM)) {
			overrideSystem = value;
			forceDTD = true;
		} else if (key.equals(VERSION)) {
			version = value;
		} else if (key.equals(STANDALONE)) {
			standalone = value;
		}
		// System.out.println("%%%% htmlMode = " + htmlMode);
	}

	// //////////////////////////////////////////////////////////////////
	// Constants.
	// //////////////////////////////////////////////////////////////////

	private final Attributes EMPTY_ATTS = new AttributesImpl();
	public static final String CDATA_SECTION_ELEMENTS = "cdata-section-elements";
	public static final String DOCTYPE_PUBLIC = "doctype-public";
	public static final String DOCTYPE_SYSTEM = "doctype-system";
	public static final String ENCODING = "encoding";
	public static final String INDENT = "indent"; // currently ignored
	public static final String MEDIA_TYPE = "media-type"; // currently ignored
	public static final String METHOD = "method"; // currently html or xml
	public static final String OMIT_XML_DECLARATION = "omit-xml-declaration";
	public static final String STANDALONE = "standalone"; // currently ignored
	public static final String VERSION = "version";

	// //////////////////////////////////////////////////////////////////
	// Internal state.
	// //////////////////////////////////////////////////////////////////

	@SuppressWarnings("rawtypes")
	private Hashtable prefixTable;
	@SuppressWarnings("rawtypes")
	private Hashtable forcedDeclTable;
	@SuppressWarnings("rawtypes")
	private Hashtable doneDeclTable;
	private int elementLevel = 0;
	private Writer output;
	private NamespaceSupport nsSupport;
	private int prefixCounter = 0;
	private Properties outputProperties;
	private boolean unicodeMode = false;
	private String outputEncoding = "";
	private boolean htmlMode = false;
	private boolean forceDTD = false;
	private boolean hasOutputDTD = false;
	private String overridePublic = null;
	private String overrideSystem = null;
	private String version = null;
	private String standalone = null;
	private boolean cdataElement = false;

}

// end of XMLWriter.java